In Colombia, various Science, Technology, and Innovation (STI) networks have emerged as pivotal enablers for the bioeconomy, even when not directly dedicated to it. These networks provide supportive conditions that strengthen bioeconomic initiatives across different phases of development. Understanding and categorizing these networks allows for a clearer view of how bioeconomy-supportive platforms evolve and impact the sector. The following categories highlight the stages, thematic focuses, and research lines relevant to these platforms.
Phases
The strengthening of the bioeconomy can be categorized into the following phases:
| Phase Types | Definition |
|---|---|
| Ideation | Platforms that originated as ideas but had limited influence on the bioeconomy |
| Planning | Platforms presenting a structured pathway aimed at supporting the bioeconomy |
| Scaling | Platforms that provide planning and scale up bioeconomy-associated businesses |
| Execution | Platforms that deploy resources to scale and support bioeconomy-focused businesses |
| Consolidation | Platforms that stabilize and strengthen bioeconomy-related businesses |
Thematic Focus
Thematic focus refers to the function or role that each platform plays in fulfilling the mission of its respective project, categorized as follows:
| Thematic Types | Definition |
|---|---|
| Connects | Platforms aimed at connecting stakeholders and information to strengthen the bioeconomy |
| Tools | Platforms that connect and create tools to facilitate bioeconomic actions |
| Accelerates | Platforms that connect, provide tools, and mobilize resources to accelerate bioeconomic enterprises |
Research Lines
Research lines represent the economic sectors each platform targets, grounded in the core mission of each project. (For better visualization, select the research lines that most capture your interest.)
This framework outlines the structure of Colombia’s enabling networks for the bioeconomy, showcasing how various STI networks contribute to fostering and accelerating bioeconomic growth across distinct phases and through specialized thematic roles and research initiatives.
Tool
aq = {
const aq = await require(`arquero@${aq_version}`);
// Add HTML table view method to tables
Object.assign(aq.ColumnTable.prototype, {
view(options) { return toView(this, options); }
});
return aq;
}toView = {
const DEFAULT_LIMIT = 100;
const DEFAULT_NULL = value => `<span style="color: #999;">${value}</span>`;
const tableStyle = 'margin: 0; border-collapse: separate; border-spacing: 0; width: initial;';
const cellStyle = 'padding: 1px 5px; white-space: nowrap; overflow-x: hidden; text-overflow: ellipsis; font-variant-numeric: tabular-nums;';
const rowStyle = 'border-bottom: 1px solid #eee;';
// given an Arquero data table, provide an HTML table view
return function(dt, opt = {}) {
// permit shorthand for limit
if (typeof opt === 'number') opt = { limit: opt };
// marshal cell color options
const color = { ...opt.color };
if (typeof opt.color === 'function') {
// if function, apply to all columns
dt.columnNames().forEach(name => color[name] = opt.color);
} else {
// otherwise, gather per-column color options
for (const key in color) {
const value = color[key];
color[key] = typeof value === 'function' ? value : () => value;
}
}
// marshal CSS styles as toHTML() options
const table = `${tableStyle}`;
const cell = (name, index, row, th) => {
return `${cellStyle} max-width: ${+opt.maxCellWidth || 300}px;`
+ ` border-bottom: solid 1px ${th ? '#ccc' : '#eee'};`
+ (color[name] ? ` background-color: ${color[name](index, row)};` : '');
};
const td = (name, index, row) => cell(name, index, row, false);
const th = (name, index, row) => `position: sticky; top: 0; background: #fff; `
+ cell(name, index, row, true);
opt = {
limit: DEFAULT_LIMIT,
null: DEFAULT_NULL,
...opt,
style: { table, td, th }
};
// return container div, bind table value to support viewof operator
const size = `max-height: ${+opt.height || 270}px`;
const style = `${size}; overflow-x: auto; overflow-y: auto;`;
const view = html`<div style="${style}">${dt.toHTML(opt)}</div>`;
return Object.assign(view, { value: dt });
};
}function Scrubber(values, {
format = value => value,
initial = 0,
direction = 1,
delay = null,
autoplay = true,
loop = true,
loopDelay = null,
alternate = false
} = {}) {
values = Array.from(values);
const form = html`<form style="font: 12px var(--sans-serif); font-variant-numeric: tabular-nums; display: flex; height: 33px; align-items: center;">
<button name=b type=button style="margin-right: 0.4em; width: 5em;"></button>
<label style="display: flex; align-items: center;">
<input name=i type=range min=0 max=${values.length - 1} value=${initial} step=1 style="width: 180px;">
<output name=o style="margin-left: 0.4em;"></output>
</label>
</form>`;
let frame = null;
let timer = null;
let interval = null;
function start() {
form.b.textContent = "Pause";
if (delay === null) frame = requestAnimationFrame(tick);
else interval = setInterval(tick, delay);
}
function stop() {
form.b.textContent = "Play";
if (frame !== null) cancelAnimationFrame(frame), frame = null;
if (timer !== null) clearTimeout(timer), timer = null;
if (interval !== null) clearInterval(interval), interval = null;
}
function running() {
return frame !== null || timer !== null || interval !== null;
}
function tick() {
if (form.i.valueAsNumber === (direction > 0 ? values.length - 1 : direction < 0 ? 0 : NaN)) {
if (!loop) return stop();
if (alternate) direction = -direction;
if (loopDelay !== null) {
if (frame !== null) cancelAnimationFrame(frame), frame = null;
if (interval !== null) clearInterval(interval), interval = null;
timer = setTimeout(() => (step(), start()), loopDelay);
return;
}
}
if (delay === null) frame = requestAnimationFrame(tick);
step();
}
function step() {
form.i.valueAsNumber = (form.i.valueAsNumber + direction + values.length) % values.length;
form.i.dispatchEvent(new CustomEvent("input", {bubbles: true}));
}
form.i.oninput = event => {
if (event && event.isTrusted && running()) stop();
form.value = values[form.i.valueAsNumber];
form.o.value = format(form.value, form.i.valueAsNumber, values);
};
form.b.onclick = () => {
if (running()) return stop();
direction = alternate && form.i.valueAsNumber === values.length - 1 ? -1 : 1;
form.i.valueAsNumber = (form.i.valueAsNumber + direction) % values.length;
form.i.dispatchEvent(new CustomEvent("input", {bubbles: true}));
start();
};
form.i.oninput();
if (autoplay) start();
else stop();
Inputs.disposal(form).then(stop);
return form;
}async function navio(data, _options = {}) {
const options = {
height: 300, // Navio's height
attribs: null, // array of attrib names to be used, leave as null for all of them
x0: 0, //Where to start drawing navio in x
y0: 100, //Where to start drawing navio in y, useful if your attrib names are too long
maxNumDistictForCategorical: 10, // addAllAttribs uses this for deciding if an attribute is categorical (has less than maxNumDistictForCategorical categories) or ordered
maxNumDistictForOrdered: 90, // addAllAttribs uses this for deciding if an attribute is ordered (has less than maxNumDistictForCategorical categories) or text. Use maxNumDistictForOrdered : Infinity for never choosing Text
howManyItemsShouldSearchForNotNull: 100, // How many rows should addAllAttribs search to decide guess an attribute type
margin: 10, // Margin around navio
levelsSeparation: 40, // Separation between the levels
divisionsColor: "white", // Border color for the divisions
levelConnectionsColor: "rgba(205, 220, 163, 0.5)", // Color for the conections between levels
divisionsThreshold: 4, // What's the minimum row height needed to draw divisions
fmtCounts: d3.format(",.0d"), // Format used to display the counts on the bottom
legendFont: "14px sans-serif", // The font for the header
nestedFilters: true, // Should navio use nested levels?
showAttribTitles: true, // Show headers?
attribWidth: 15, // Width of the columns
attribRotation: -45, // Headers rotation
attribFontSize: 13, // Headers font size
attribFontSizeSelected: 32, // Headers font size when mouse over
filterFontSize: 10, // Font size of the filters explanations on the bottom
tooltipFontSize: 12, // Font size for the tooltip
tooltipBgColor: "#b2ddf1", // Font color for tooltip background
tooltipMargin: 50, // How much to separate the tooltip from the cursor
tooltipArrowSize: 10, // How big is the arrow on the tooltip
digitsForText: 2, // How many digits to use for text attributes
addAllAttribsRecursionLevel: Infinity, // How many levels depth do we keep on adding nested attributes
addAllAttribsIncludeObjects: true, // Should addAllAttribs include objects
addAllAttribsIncludeArrays: true, // Should addAllAttribs include arrays
nullColor: "#ffedfd", // Color for null values
defaultColorInterpolator: d3.interpolateBlues,
defaultColorInterpolatorDate: d3.interpolatePurples,
defaultColorInterpolatorDiverging: d3.interpolateBrBG,
defaultColorInterpolatorOrdered: d3.interpolateOranges,
defaultColorInterpolatorText: d3.interpolateGreys,
defaultColorRangeBoolean: ["#a1d76a", "#e9a3c9", "white"], //true false null
defaultColorRangeSelected: ["white", "#b5cf6b"],
defaultColorCategorical: d3.schemeCategory10,
showSelectedAttrib: true, // Display the attribute that shows if a row is selected
showSequenceIDAttrib: true, // Display the attribute with the sequence ID
..._options
};
let div = html`<div style="display:block; overflow-x:scroll"></div>`;
// Create the navio
const nv = navio_npm(d3.select(div), options.height);
for (let opt in options) {
if (opt === "id") {
nv.id(options[opt]);
} else if (opt !== "attribs") {
nv[opt] = options[opt];
}
}
// Add the data
nv.data(data);
if (options.attribs) {
nv.addAllAttribs(options.attribs);
} else {
nv.addAllAttribs();
}
nv.updateCallback(() => {
div.value = nv.getVisible();
div.dispatchEvent(new Event("input", { bubbles: true }));
// notify(div);
});
div.value = data;
div.nv = nv;
return div;
}data_hubs0 = FileAttachment("Hubs_bioec_col2.csv").csv({ typed: true })
data_hubs = data_hubs0.map(d => ({ ...d, name: d.platform, type: "Bioeconomy" }));hullColor = d3.scaleOrdinal(
d3.quantize(
colorInterpolator,
tidyData.groupby(groupBy).count().objects().length
)
)graph = {
for (let n of dData.values()) {
n.degree = 0;
}
const linksArray = links.objects();
for (let l of linksArray) {
const source = dData.get(l.source);
// target = dNodes.get(l.target);
source.cluster = l.target;
// target.cluster = l.target;
source.degree += 1;
// target.degree += 1;
}
const getOrCreateNode = (name) => {
let node = dConnections.get(name);
if (!node) {
node = {
name,
type: "Connection",
cluster: name,
x: width / 2 + (Math.random() * width) / 10,
y: fheight_node.height / 2 + (Math.random() * fheight_node.height) / 10
};
dConnections.set(name, node);
}
return node;
};
// --------- Filter Links and Nodes --------------
const filteredLinks = linksArray.map((l) => {
return {
source: dData.get(l.source),
target: getOrCreateNode(l.target)
};
});
const filteredNodesObject = filteredLinks.reduce(
(p, l) => {
if (!p.set.has(l.source.name)) {
p.set.add(l.source.name);
p.list.push(l.source);
}
if (!p.set.has(l.target.name)) {
p.set.add(l.target.name);
p.list.push(l.target);
}
return p;
},
{ set: new Set(), list: [] }
);
// Clusters is used for the hulls
const clusters = d3.rollups(
filteredLinks,
(v) => v.map((l) => l.source).concat([v[0].target]),
(d) => d.target.name
);
return {
nodes: filteredNodesObject.list,
links: linksArray,
clusters: clusters
};
}tidyData = aq
.from(data_hubs)
.derive({
"research_line": (d) =>
d["research_line"] === null ? [null] : op.split(d["research_line"], ";"),
"departments": (d) =>
d["departments"] === null ? [null] : op.split(d["departments"], ";")
})
.unroll("research_line")
.unroll("departments")links = tidyData
.filter(aq.escape((d) => !d.year || d.year <= minDate)) // Filter by Date
.derive({
source: (d) => d.name,
target: aq.escape((d) => d[groupBy])
})
.groupby(["source", "target"])
.count()
.filter((d) => d.target && d.source) // Ignore nulls
.filter(aq.escape((d) => selectedTargets.includes(d.target))) // Filter by selectedTarget
.select(["source", "target"])sources = links
.groupby("source")
.count()
.rename({ source: "name" })
.derive({ type: () => "Bioeconomy" })template_2 = (inputs) =>
htl.html`
<div class="styled-template-2">
${Object.values(inputs)}
</div>
<style>
.styled-template-2 {
text-align: left;
column-count: 2; /* Mantiene 2 columnas */
column-gap: 20px; /* Espacio entre columnas */
max-width: 600px; /* Ancho máximo del formulario */
margin-left: 0; /* Alinea a la izquierda */
}
.styled-template-2 label {
font-weight: bold;
line-height: 200%;
}
.styled-template-2 label:not(div>label)::after {
content: ":";
}
</style>
`;template_4 = (inputs) =>
htl.html`
<div class="styled">
${Object.entries(inputs).map(([key, input]) => {
// Extract the label text from the input, if any, and remove it to prevent duplication
const labelText = input.querySelector('label') ? input.querySelector('label').textContent : "";
if (input.querySelector('label')) input.querySelector('label').remove();
return htl.html`
<div class="input-group">
<label>${labelText}</label>
${input}
</div>
`;
})}
</div>
<style>
div.styled {
text-align: left;
column-count: 4; /* Set to 4 columns */
column-gap: 20px; /* Space between columns */
}
div.input-group {
display: flex;
flex-direction: column;
margin-bottom: 10px; /* Space between inputs */
}
div.input-group label {
font-weight: bold;
margin-bottom: 5px; /* Space between label and control */
}
</style>
`;viewof minDate = {
const dates = data_hubs
.map((d) => d.year)
.filter((d) => d)
.sort((a, b) => d3.ascending(+a, +b));
return Scrubber(dates, {
delay: 500,
autoplay: false,
loop:false,
initial: dates.length-1
});
}viewof groupBy = {
const attrs = [
//"plataforma",
//"descripcion",
//"link",
"thematic",
"phases",
"relation",
"research_line",
//"partners",
//"country_host",
////"year",
//"departamentos",
//"def_bioeconomia"
//"relaciones"
//"logo"
//"logo_viz"
//"lat_host"
//"long_host"
];
return Inputs.select(attrs, { label: "Group", value: "phases" });
}viewof selectedTargets = {
const targetNames = tidyData
.groupby(groupBy)
.count()
.objects()
.map((d) => d[groupBy])
.filter((d) => d);
const selection =
groupBy === "research_line"
? targetNames.filter((d) => !["Bioindustría","Bioenergía","Forestal"].includes(d))
: targetNames;
const cr = 15;
return Inputs.checkbox(targetNames, {
label: "Show",
value: selection,
format: (d) => html`<div style="height: ${cr}px;
line-height: ${cr}px;
display: flex;
align-content: center;
flex-wrap: wrap;
"><svg width=${cr} height=${cr} style="margin-right:5px">
<circle r=${cr / 2} cx=${cr / 2} cy=${cr / 2} fill="${hullColor(d)}"/>
</svg>
${d}</div>`
});
}viewof fheight_node = (
Inputs.form({
height: Inputs.range([200, 1400], {label: "Height", step:1, value: 420}),
nodeSize: Inputs.range([2, 30], {label: "Node Size",value: 23,step: 1})},
{
template: template_2
})
)chart = {
const svg = d3.create("svg")
.attr("width", width)
.attr("height", fheight_node.height)
.attr("viewBox", [0, 0, width, fheight_node.height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic; overflow: visible");
graph.nodes.forEach(function (d) {
d.r = d.type === "Bioeconomy" ? fheight_node.nodeSize * (d.degree / 2 + 1) : fhpadding_box.greyNodeSize;
});
const chargeScale = d3.scaleLinear()
.domain([20, 160])
.range([width < 800 ? -100 : -200, width < 800 ? -50 : -100]);
const chargeStrength = function (d) {
return (fheight_node.nodeSize / 10) * (d.degree > 1 ? 1 : 0.7) * chargeScale(graph.nodes.length);
};
const simulation = d3.forceSimulation(graph.nodes)
.alpha(3)
.force(
"link",
d3.forceLink(graph.links)
.id(d => d.name)
.distance(10)
.strength(0.1)
)
.force("charge", d3.forceManyBody().strength(chargeStrength))
.force("collide", d3.forceCollide(d => d.r + 3).iterations(4))
.force("x", d3.forceX(width / 2).strength(0.1))
.force("y", d3.forceY(fheight_node.height / 2).strength((0.07 * width) / fheight_node.height));
const area = d3.line().curve(d3.curveCardinalClosed);
const hp = fhpadding_box.hullPadding;
const getHull = function (d) {
const points = d[1]
.filter(d => fclabels_check.showGroupingNodes || d.type === "Bioeconomy")
.map(n => [
[n.x - (n.r + hp), n.y],
[n.x + n.r + hp, n.y],
[n.x, n.y + n.r + hp],
[n.x, n.y - (n.r + hp)]
])
.flat();
const hull = d3.polygonHull(points);
return hull && area(hull);
};
const hullG = svg.append("g").attr("id", "gHulls");
const hull = hullG
.selectAll("path.hull")
.data(
graph.clusters.filter(d => !true || d[0] !== "Khoury College of Computer Sciences"),
d => d[0]
)
.join("path")
.attr("class", "hull")
.attr("fill", d => hullColor(d[0]))
.attr("opacity", 0.3)
.attr("pointer-events", "none");
const linkG = svg.append("g").attr("id", "gLinks");
const link = linkG
.attr("stroke", "#999a")
.attr("stroke-opacity", 1)
.attr("stroke-width", 0.5)
.attr("stroke-linecap", "round")
.selectAll("line")
.data(
graph.links.filter(d => fclabels_check.showGroupingNodes || d.target.type === "Bioeconomy"),
d => `${d.source.name}-${d.target.name}`
)
.join("line");
const nodeG = svg.append("g").attr("id", "gNodes");
// Crear labels para Bioeconomy y nodos grises primero
const getNameArray = function (d) {
return d.nameArray ? d.nameArray : d.name.split(" ");
};
const lineWidth = 1.9;
const createLabels = function (_data, className = "BioeconomyLabel") {
return svg
.selectAll(`text.${className}`)
.data(_data, d => d.name)
.join("text")
.attr("class", className)
.attr("text-anchor", "middle")
.attr("font-size", d => `${fhpadding_box.fontSize * (d.type === "Bioeconomy" ? d.r / fheight_node.nodeSize / 2 : 1.8)}pt`)
.attr("font-weight", d => (d.type === "Bioeconomy" ? "light" : "bolder"))
.attr("font-family", "Oswald")
.attr("pointer-events", "none")
.style("clip-path", d => fclabels_check.clipLabels && d.type === "Bioeconomy" ? `circle(${d.r - 2}px)` : "")
.each(function (d) {
d3.select(this)
.selectAll("tspan")
.data(getNameArray(d))
.join("tspan")
.attr("x", 0)
.attr("dy", lineWidth + "ch")
.text(n => n);
})
.attr("y", d => (getNameArray(d).length / 2) * -lineWidth + "ch");
};
const label = createLabels(
graph.nodes.filter(d => d.type === "Bioeconomy" ? d.degree >= fhpadding_box.minDegree : 0),
"BioeconomyLabel"
);
const labelDepts = createLabels(
graph.nodes.filter(d => d.type !== "Bioeconomy"),
"departmentLabel"
);
// Crear nodos y añadir imágenes sobre los labels
const node = nodeG
.selectAll("circle")
.data(
graph.nodes.filter(d => fclabels_check.showGroupingNodes || d.type === "Bioeconomy"),
d => d.name
)
.join("circle")
.attr("fill", d => (d.type !== "Bioeconomy" ? "#999" : hullColor(d.cluster)))
.attr("stroke-opacity", 1)
.attr("stroke-width", 1)
.attr("r", d => d.r)
.call(drag(simulation));
const images = nodeG
.selectAll("image")
.data(
fclabels_check.useImages
? graph.nodes.filter(d => d.logo_viz)
: [],
d => d.name
)
.join("image")
.attr("href", d => d.logo_viz)
.attr("width", d => d.r * 2 - 4)
.attr("height", d => d.r * 2 - 4)
.attr("stroke", "black")
.attr("preserveAspectRatio", "xMidYMid slice")
.attr("transform", d => `translate(-${d.r - 2}, -${d.r - 2})`)
.style("clip-path", d => `circle(${d.r - 2}px)`)
.call(drag(simulation));
function updateVisibility() {
label.attr("display", fclabels_check.useImages ? "none" : "block");
images.attr("display", fclabels_check.useImages ? "block" : "none");
}
updateVisibility();
function ticked() {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
label.attr("transform", d => `translate(${d.x}, ${d.y})`).classed("shadow", simulation.alpha() <= 0.1);
labelDepts.attr("transform", d => `translate(${d.x}, ${d.y})`).classed("shadow", simulation.alpha() <= 0.1);
node.attr("cx", d => d.x).attr("cy", d => d.y).classed("shadow", simulation.alpha() <= 0.1);
if (fclabels_check.useImages) {
images.attr("x", d => d.x).attr("y", d => d.y);
}
hull.attr("d", getHull);
}
simulation.on("tick", ticked);
function drag(simulation) {
function dragstarted(event) {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}
function dragged(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}
function dragended(event) {
if (!event.active) simulation.alphaTarget(0);
event.subject.fx = null;
event.subject.fy = null;
}
return d3
.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
invalidation.then(() => simulation.stop());
ticked();
return htl.html`<div>
<h2> bioeconomy enabling networks by ${groupBy}</h2>
${svg.node()}
</div>`;
};viewof colorInterpolator = {
const colorSchemes = [
"interpolateBlues",
"interpolateBrBG",
"interpolateRdBu",
"interpolateRdYlGn",
"interpolateReds",
"interpolateSinebow",
"interpolateSpectral",
"interpolateTurbo",
"interpolateViridis",
];
const colors = new Map(
[["Custom Turbo", (t) => d3.interpolateTurbo(0.2 + t * 0.8)]].concat(
colorSchemes.map((c) => [c, d3[c]])
)
);
return Inputs.select(colors, {
label: "Color Scheme",
format: (d) => d[0]
});
}viewof fhpadding_box = (
Inputs.form({
hullPadding: Inputs.range([0, 100], {label: "Hull Padding", value: 9, step: 1}),
minDegree: Inputs.range([0, d3.max(graph.nodes, (d) => d.degree) + 1], {
label: "Min Degree for Label",
value: 1,
step: 1
}),
fontSize: Inputs.range([1, 36], {label: "Font size", value: 8, step: 1}),
greyNodeSize: Inputs.range([1, 30], {label: "Connecting Node Size", value: 5, step: 1})
},
{
template: template_4 // Específicamente usando template_4 para 4 columnas
})
);viewof fclabels_check = (
Inputs.form({
useImages:Inputs.toggle({label: "Use Images", value: true}),
showGroupingNodes:Inputs.toggle({label: "Show grey nodes", value: true}),
clipLabels: Inputs.toggle({label: "Clip Labels", value: true}),
useShadowsText: Inputs.toggle({label: "Use Text Shadows", value: true})},
{
template: template_4
})
)Colombian Bioeconomy Network
Thanks to the identification of these enabling networks, an event was organized under the Bioeconomy Hub Project of Bogotá Region to establish the Colombian Bioeconomy Network. This initiative utilized primary information from attendees of the event, whose profiles revolved around bioeconomy-related topics. The findings highlighted 54 actors with 133 connections, emphasizing the central roles of Instituto Humboldt, Biointropic, and Minciencias as key players driving bioeconomy in Colombia. Additionally, it was noted that some attendees indicated they currently have no connections with other bioeconomy actors but expressed interest in participating in the process.
The network map below illustrates the connections and interactions between these actors, showcasing the collaborative potential and synergies essential for advancing bioeconomic initiatives in the country.